﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace peonwar
{
    public class IA : User
    {

        //PROPRIETES
        private Difficulty LevelIA;
        private Random dé1;
        private Random dé2;
        private bool[] rsrchTemplate;
        // CONSTRUCTEUR
        public IA(bool team, Difficulty level, string n, Game g)
            : base(team, n, g)
        {
            this.LevelIA = level;
            this.isIA = true;
            dé1 = new Random();
            dé2 = new Random();
            rsrchTemplate = new bool[3];
            for (int i = 0; i < 3; i++)
                if (dé1.Next(2) == 0)
                    rsrchTemplate[i] = false;
                else
                    rsrchTemplate[i] = true;
        }

        //METHODES
        public static string GetIA(Difficulty diff)
        {
            switch (diff)
            {
                case Difficulty.Training:
                    return GameData.GameString[40];
                case Difficulty.Easy:
                    return GameData.GameString[41];
                case Difficulty.Medium:
                    return GameData.GameString[42];
                case Difficulty.Hard:
                    return GameData.GameString[43];
                case Difficulty.Empowered:
                    return GameData.GameString[44];
                case Difficulty.Hopeless:
                    return GameData.GameString[45];
                default:
                    return GameData.GameString[158];
            }
        }
        public override void Play(User enemy)
        {
            base.Play(enemy);
            Unit unit; 
            /* *********************************
            *             IA TRAINING
            *          
            *     Balance des unitées de temps 
            *     en temps, histoire de faire 
            *     quelque chose.
            * 
            * ********************************/
            #region
            if (LevelIA == Difficulty.Training)
            {
                this.IncomeLevel = 0;
                if (PeonList.Count < 2 && this.Cooldown >= 100)
                {
                    int unitALancer;
                    unitALancer = dé1.Next(8);
                    if (!this.AvailableUnits[unitALancer])
                        unitALancer = CheckUnitAvailable();
                    unit = popUnit(unitALancer);
                    if ((unit.UnitType > 3 && Level < 1) || (unit.UnitType > 6 && Level < 2))
                        unit = new UnitLight3(this);
                    if (this.Bank > unit.Cost)
                        SpawnUnit(unit);
                    else
                    {

                        Tools.BankManage(this, unit.Cost);
                        SpawnUnit(unit);
                        this.Cooldown -= this.Energy[unit.UnitType - 1] * 2;
                    }
                }

            }
            #endregion

            /* *********************************
             *             IA FACILE
             *
             * Balance des unitées en fonction de l'armée
             * enemi
             * De temps en temps, attend un peu avant de 
             * reflechir a une prochaine action
             * Adaptation tres basique a larmee enemi
             * 
             * ********************************/
            #region
            if (LevelIA == Difficulty.Easy)
            {
                if (this.Cooldown >= 100 && PeonList.Count < 5 && PeonBuffer.Count < 3)
                {
                    if (dé1.Next(3) == 0)
                        Cooldown -= 60;
                    else
                    {
                        int unitALancer = GeneralTargeting(0, enemy);

                        // Gestions des cas vraiment specifiques
                        if (this.Bank > 2000 && Level > 3)
                            unitALancer = 8;
                        if (unitALancer > 2 && Level < 1)
                            unitALancer = 2;
                        if (unitALancer > 5 && Level < 2)
                            unitALancer = 2;
                        if (unitALancer == 8 && Bank < 200)
                            unitALancer = 0;

                        if (!this.AvailableUnits[unitALancer])
                            unitALancer = CheckUnitAvailable();

                        unit = popUnit(unitALancer);

                        if (this.Bank > unit.Cost)
                            this.SpawnUnit(unit);
                        else
                        {
                            Tools.BankManage(this, unit.Cost);
                            this.SpawnUnit(unit);
                            this.Cooldown -= this.Energy[unit.UnitType - 1] * 6;
                        }
                    }
                }
            }
            #endregion

            /* ********************************
            *          IA MOYENNE
            *          
            * Ameliore ses unités
            * Balance des unitées en fonction de l'armée
            * enemi
            * De temps en temps ce goure et balance une unitée randomly
            * De temps en temps, attend un peu avant de reflechir a une prochaine action
            * Developpe des recherches
            * 
            * ********************************/
            #region
            if (LevelIA == Difficulty.Medium)
            {
                if (this.Cooldown >= 100 && PeonList.Count < 6 && PeonBuffer.Count < 2)
                {
                    if (dé1.Next(4) == 0)
                        Cooldown -= 50;
                    else
                    {
                        if (dé1.Next(4) == 0 && Level > 0) //Une chance sur quatre que l'IA essaye d'ameliorer ses techs
                            UpgradeUnitAml();
                        else
                        {
                            int unitALancer = 0;
                            if (dé1.Next(5) == 0)
                                unitALancer = dé1.Next(8);
                            else
                            {
                                if (!MassSamos(enemy))// si pas masssamos
                                {
                                    // La prochaine partie consistera a , si l'armée de l'IA est vide ou presque
                                    // Envoyer une unitée correspondant au premier peon de la liste enemi
                                    if (enemy.PeonList.Count > 0 && (this.PeonList.Count < 3 || enemy.PeonList[0].kill > 3))
                                        unitALancer = SpecificTargeting(unitALancer, enemy);
                                    // La partie qui va suivre est , c'est vrai, carrement wtf...
                                    // Mais croyez moi vaut mieu faire ca que des else if dans des else if ...
                                    else
                                        unitALancer = GeneralTargeting(unitALancer, enemy);
                                }
                                else
                                {
                                    unitALancer = 2;
                                    if( CanUpGrade[0, 0] && Upgrade(0, 0))
                                        CanUpGrade[0, 0] = false;
                                }
                            }
                            // Gestions des cas vraiment specifiques
                            if (this.Bank > 2000 && Level > 3)
                                unitALancer = 8;
                            if (unitALancer > 2 && Level < 1)
                                unitALancer = 2;
                            if (unitALancer > 5 && Level < 2)
                                unitALancer = 2;
                            if (unitALancer == 8 && Bank < 200)
                                unitALancer = 0;

                            if (!this.AvailableUnits[unitALancer])
                                unitALancer = CheckUnitAvailable();

                            unit = popUnit(unitALancer);

                            if (this.Bank > unit.Cost)
                                this.SpawnUnit(unit);
                            else
                            {
                                Tools.BankManage(this, unit.Cost);
                                this.SpawnUnit(unit);
                                this.Cooldown -= this.Energy[unit.UnitType - 1] * 5;
                            }
                        }
                    }
                }
            }
            #endregion

            /* *******************************
            *             IA DIFFICILE
            *          
            *    Effectue tout les types d'ameliorations.
            *    Analyse sa derniere unitée lancé pour choisir
            *    si y'a besoin d'une unitée de suporte derriere
            * 
            * ********************************/
            #region
            if (LevelIA == Difficulty.Hard)
            {
                if (this.Cooldown >= 100 && PeonList.Count < 7 && PeonBuffer.Count < 1)
                {

                    if (dé1.Next(5) == 0)
                        Cooldown -= 40;
                    else
                    {
                        // AMELIORATION UNITEE
                        if (dé1.Next(4) == 0)//Une chance sur quatre que l'IA essaye d'ameliorer ses techs
                            UpgradeUnitAml();
                        // AMELIORATIONS BASES
                        else if (dé1.Next(4) == 0) //Une chance sur quatre que l'IA essaye d'ameliorer ses techs de la base
                            UpgradeBaseAml(enemy);
                        // CREER UNE UNITEE
                        else
                        {
                            int unitALancer = 0;

                            if (!MassSamos(enemy))
                                if (enemy.PeonList.Count > 0 && (this.PeonList.Count < 2 || enemy.PeonList[0].kill > 3))
                                    unitALancer = SpecificTargeting(unitALancer, enemy);
                                else
                                    unitALancer = GeneralTargeting(unitALancer, enemy);
                            else
                            {
                                if (Level < 2)
                                {
                                    unitALancer = 2;
                                    if (CanUpGrade[0, 0] && Upgrade(0, 0))
                                        CanUpGrade[0, 0] = false;
                                }
                                else
                                    unitALancer = 6;
                            }

                            // Examinons si la derniere unitée a avoir été lancé necessite un support...
                            if (PeonList.Count > 0)
                                unitALancer = SendSupport(unitALancer, enemy);

                            // Gestions des cas vraiment specifiques
                            if (this.Bank > 2000 && Level > 3)
                                unitALancer = 8;
                            if (unitALancer == 0 && this.TechSpeed[0] < 2)
                                unitALancer = dé1.Next(3);
                            if (unitALancer == 4 && this.TechSpeed[1] < 2)
                                unitALancer = 1;
                            if (unitALancer > 2 && Level < 1)
                                unitALancer = dé1.Next(3);
                            if (unitALancer > 5 && Level < 2)
                                unitALancer = dé1.Next(3);
                            if (unitALancer == 8 && Bank < 200)
                                unitALancer = 6;

                            if (!this.AvailableUnits[unitALancer])
                                unitALancer = CheckUnitAvailable();

                            unit = popUnit(unitALancer);
                            if (this.Bank > unit.Cost)
                                this.SpawnUnit(unit);
                            else
                            {
                                if (this.PeonList.Count == 0)
                                    this.Bank += 75;
                                this.Bank += unit.Cost;
                                this.SpawnUnit(unit);
                                this.Cooldown -= this.Energy[unit.UnitType - 1] * 4;
                            }
                        }
                    }
                }
            }
            #endregion

            /* *********************************
            *             IA SURPUISSANTE
            *      Lance plus d'unités
            *      Evolue sa base en fonction du joueur
            *      Sa technologie de income est direct a 2
            *      N'attend jamais avant d'effectuer une action possible
            *      Utilise les pouvoir de temps en temps
            *       
            * ********************************/
            #region
            if (LevelIA == Difficulty.Empowered)
            {

                if (IncomeLevel < 2)
                    IncomeLevel = 2;
                if (this.Cooldown >= 100 && PeonList.Count < 8 && PeonBuffer.Count < 1)
                {
                    // AMELIORATION UNITEE
                    if (dé1.Next(4) == 0)//Une chance sur quatre que l'IA essaye d'ameliorer ses techs
                        UpgradeUnitAml();

                    // AMELIORATIONS BASES
                    else if (dé1.Next(4) == 0) //Une chance sur quatre que l'IA essaye d'ameliorer ses techs de la base
                        UpgradeBaseAml(enemy);

                    // POUVOIR
                    else if (Level > 0 && dé1.Next(3) == 2)// Une chance sur 3 qu'on balance un pouvoir
                        usePower();

                    // CREER UNE UNITEE
                    else
                    {
                        int unitALancer = 0;

                        if (!MassSamos(enemy))
                            if (enemy.PeonList.Count > 0 && (this.PeonList.Count < 2 || enemy.PeonList[0].kill > 3))
                                unitALancer = SpecificTargeting(unitALancer, enemy);
                            else
                                unitALancer = GeneralTargeting(unitALancer, enemy);
                        else
                        {
                            if (Level < 2)
                            {
                                unitALancer = 2;
                                if (CanUpGrade[0, 0] && Upgrade(0, 0))
                                    CanUpGrade[0, 0] = false;
                            }
                            else
                                unitALancer = 6;
                        }

                        // Examinons si la derniere unitée a avoir été lancé necessite un support...
                        if (PeonList.Count > 0)
                            unitALancer = SendSupport(unitALancer, enemy);

                        // Gestions des cas vraiment specifiques
                        if (this.Bank > 2000 && Level > 3)
                            unitALancer = 8;
                        if (unitALancer == 0 && this.TechSpeed[0] < 2)
                            unitALancer = dé1.Next(3);
                        if (unitALancer == 4 && this.TechSpeed[1] < 2)
                            unitALancer = 1;
                        if (unitALancer > 2 && Level < 1)
                            unitALancer = dé1.Next(3);
                        if (unitALancer > 5 && Level < 2)
                            unitALancer = dé1.Next(3);
                        if (unitALancer == 8 && Bank < 200)
                            unitALancer = 6;

                        if (!this.AvailableUnits[unitALancer])
                            unitALancer = CheckUnitAvailable();

                        unit = popUnit(unitALancer);
                        if (this.Bank > unit.Cost)
                            this.SpawnUnit(unit);
                        else
                        {
                            if (this.PeonList.Count == 0)
                                this.Bank += 100;
                            this.Bank += unit.Cost;
                            this.SpawnUnit(unit);
                            this.Cooldown -= this.Energy[unit.UnitType - 1] * 3;
                        }
                    }
                }
            }
            #endregion

            /* *********************************
            *             IA CAUCHEMARDESQUE  
            *      Sa technologie de income est direct a 4
            *      N'attend jamais avant d'effectuer une action possible
            *      Utilise les pouvoir en masse
            *      
            * ********************************/
            #region
            if (LevelIA == Difficulty.Hopeless)
            {

                if (IncomeLevel < 4)
                    IncomeLevel = 4;
                if (this.Cooldown >= 100 && PeonList.Count < 8 && PeonBuffer.Count < 1)
                {
                    // AMELIORATION UNITEE
                    if (dé1.Next(4) == 0)//Une chance sur quatre que l'IA essaye d'ameliorer ses techs
                        UpgradeUnitAml();
                    // AMELIORATIONS BASES
                    else if (dé1.Next(4) == 0) //Une chance sur quatre que l'IA essaye d'ameliorer ses techs de la base
                        UpgradeBaseAml(enemy);
                    // CREER UNE UNITEE
                    else
                    {
                        usePower();// Avant de balancer une unité, je check si je peux balancer un pouvoir
                        int unitALancer = 0;

                        if (!MassSamos(enemy))
                            if (enemy.PeonList.Count > 0 && (this.PeonList.Count < 2 || enemy.PeonList[0].kill > 3))
                                unitALancer = SpecificTargeting(unitALancer, enemy);
                            else
                                unitALancer = GeneralTargeting(unitALancer, enemy);
                        else
                        {
                            if (Level < 2)
                            {
                                unitALancer = 2;
                                if (CanUpGrade[0, 0] && Upgrade(0, 0))
                                    CanUpGrade[0, 0] = false;
                            }
                            else
                                unitALancer = 6;
                        }

                        // Examinons si la derniere unitée a avoir été lancé necessite un support...
                        if (PeonList.Count > 0)
                            unitALancer = SendSupport(unitALancer, enemy);

                        // Gestions des cas vraiment specifiques
                        if (this.Bank > 1000 && Level > 3)
                            unitALancer = 8;
                        if (unitALancer == 0 && this.TechSpeed[0] < 2)
                            unitALancer = dé1.Next(3);
                        if (unitALancer == 4 && this.TechSpeed[1] < 2)
                            unitALancer = 1;
                        if (unitALancer > 2 && Level < 1)
                            unitALancer = dé1.Next(3);
                        if (unitALancer > 5 && Level < 2)
                            unitALancer = dé1.Next(3);
                        if (unitALancer == 8 && Bank < 200)
                            unitALancer = 6;

                        if (!this.AvailableUnits[unitALancer])
                            unitALancer = CheckUnitAvailable();

                        unit = popUnit(unitALancer);
                        if (this.Bank > unit.Cost)
                            this.SpawnUnit(unit);
                        else
                        {
                            if (this.PeonList.Count == 0)
                                this.Bank += 120;
                            this.Bank += unit.Cost;
                            this.SpawnUnit(unit);
                            this.Cooldown -= this.Energy[unit.UnitType - 1] * 2;
                        }
                    }
                }
            }
            #endregion

            // MERCI DE NE PAS TOUCHER A MON CODE LIE AUX IAs! MERCI ^^
        }

        #region fonctionsIA

        private int CheckUnitAvailable()
        {

            int r = dé2.Next(9);
            while (!this.AvailableUnits[r])
                r = dé2.Next(9);
            return r;
        }

        private bool PowerIsAvailable(ButtonTimed BT)
        {
            if (BT.Available && BT.Countdown.ElapsedTime == BT.Countdown.Duration)
                return true;
            return false;
        }

        private void UpgradeUnitAml()
        {
            int LayerSelected = dé2.Next(2); // Quel classe d'unité ameliorer?
            int fiU = LayerSelected * 3;
            while (!this.AvailableUnits[fiU] && !this.AvailableUnits[fiU + 1] && !this.AvailableUnits[fiU + 1])
            {
                LayerSelected++;
                LayerSelected %= 3;
                fiU = LayerSelected * 3;
                //Attention à toujours avoir une unité envoyable
            }
            if (TechSpeed[0] + TechPower[0] + TechResist[0] > 3 * ((int)LevelIA - 2))
                LayerSelected = 1;
            if (TechSpeed[1] + TechPower[1] + TechResist[1] > 3 * ((int)LevelIA - 2))
                LayerSelected = 2;
            if (TechSpeed[2] + TechPower[2] + TechResist[2] > 3 * ((int)LevelIA - 2))
                LayerSelected = 0;

            if (LayerSelected > 0)
            {
                int amelioration = dé2.Next(4);// On selectionne au hasard une technologie

                if (this.TechSpeed[LayerSelected] < 2)
                    amelioration = 3; // Mais en prioritée la technologie de speed (Parceque debloque des unitées chez l'IA)
                int amount = Cost[LayerSelected, amelioration] + 50;
                if (PeonList.Count >= 5 && Bank < 150 && amount <= (500 * ((int)LevelIA - 1)))
                    Bank = amount;
                if (this.Bank > Cost[LayerSelected, amelioration] && CanUpGrade[LayerSelected, amelioration] && Upgrade(LayerSelected, amelioration))
                    CanUpGrade[LayerSelected, amelioration] = false;
            }
        }

        private void UpgradeBaseAml(User enemy)
        {
            // ON a de l'argent... on l'investit: Soit dans la baisse des prix des unités soit dans augmentation income
            if (this.Bank > 1000 && (int)LevelIA <= 3 && this.Bank > (this.BaseHealthLevel * this.BaseHealthLevel * 200) && this.BaseHealth > 8000)
                this.UpgradeBaseArmor();
            else if ((int)LevelIA > 3 && this.BaseHealthLevel < enemy.BaseHealthLevel || (this.Bank > (this.BaseHealthLevel * this.BaseHealthLevel * 200) && this.BaseHealth > 8000))
            {
                if (this.Bank < (this.BaseHealthLevel * this.BaseHealthLevel * 200))
                    this.Bank = this.BaseHealthLevel * this.BaseHealthLevel * 200;
                this.UpgradeBaseArmor();
            }
            else if (this.Bank > 1200 && dé2.Next(2) == 0 && this.Bank > (this.ProductionLevel * this.ProductionLevel * 200))
                this.UpgradeProductionLevel();
            else if (this.Bank > this.IncomeLevel * 150)
                this.UpgradeIncome();
        }

        private void usePower()
        {
            if (PowerIsAvailable(PowersList[4]))
                Power_SuperPeon();

            if (PeonList.Count > 0)
            {
                if (PowerIsAvailable(PowersList[1]) && PeonList[0].DeadTime == 0 && PeonList[0].Health < (PeonList[0].MaxHealth / 2) && (PeonList[0].kill > 5 || (PeonList[0].UnitType > 5)))
                    Power_Heal();
                if (this.Cooldown <= 10 && PowerIsAvailable(PowersList[0]))
                    Power_Drain(Game.p1_player);
            }

            if (PowerIsAvailable(PowersList[2]) && (Game.p1_player.PeonList.Count + 3 > Game.p2_opponent.PeonList.Count))
                Power_BombDamaging(Game.p1_player);

            if (PowerIsAvailable(PowersList[3]) && Bank > 400)
                Power_Cresus();
        }


        private bool MassSamos(User enemy)
        {
            int nbSamo = 0;
            foreach (Unit peon in enemy.PeonList)
                if (peon.UnitType == 1)
                    nbSamo++;
            if (nbSamo > 6)
                return true;
            return false;
        }

        private int SpecificTargeting(int unitALancer, User enemy)
        {
            Unit uniteeAdverse = enemy.PeonList[0];
            /*
            float killsennemis = 0;
            if (PeonList.Count > 4)
            {
                for (int i = 0; i < 4; i++)
                    killsennemis += enemy.PeonList[i].kill;

                    killsennemis /= 4;
            }

            if (killsennemis > 5)
                unitALancer = 8;
            else
             */
            switch (uniteeAdverse.UnitType)
            {
                case 9:
                case 8:
                case 7:
                    unitALancer = 5; // Bazzok
                    break;
                case 6:
                    unitALancer = 3; // Demineur
                    break;
                case 5:
                    unitALancer = 1; // Sniper
                    break;
                case 4:
                    unitALancer = 4; // MiniGun
                    break;
                case 3:
                case 2:
                    unitALancer = 7; // Tank
                    break;
                case 1:
                    unitALancer = 6; // Jeep
                    break;
                default:
                    unitALancer = 3;
                    break;
            }
            if (uniteeAdverse.kill > 12)
                unitALancer = 8;
            return unitALancer;
        }

        private int GeneralTargeting(int unitALancer, User enemy)
        {

            // Cette partie permet de determiner la tactique 
            // du joueur ( en gros si il fait mass poids lourds ou pas )
            // typeArmée > 6.2 Armée anti-leger
            // || typeArmée > 5 Armée infenterie
            // || typeArmée > 3 Armée Polyvalente
            // || typeArmée > 1.5 Armée vehicules/Moyens 
            // || typeArmée > 0 Armée de Blindées 
            double typeArmee = 0;
            if (enemy.PeonList.Count > 0)
            {
                foreach (Unit Peon in enemy.PeonList)
                    typeArmee += 8 - Peon.UnitType;
                typeArmee /= enemy.PeonList.Count;
            }
            else
                typeArmee = 4;

            if (typeArmee < 1.5) // Armée Blindées
                unitALancer = 5; // 100% bazzoks
            else if (typeArmee < 3)// Armée Vehicules Moyenne
                unitALancer = dé1.Next(2) * dé1.Next(2) + 3; // 1/3 Demineurs, 1/3 Bazzoks, 1/3 Tanks
            else if (typeArmee < 5)// Armée Poly
                unitALancer = dé1.Next(8);
            else if (typeArmee < 6.2)// Armée infenterie
            {
                if (dé1.Next(2) == 0)
                    unitALancer = 0;
                else
                    unitALancer = (dé2.Next(2) * 2) + 6;
                // 50% Berserkers( et pas Samo), 25% Jeeps, 25% Robots
            }
            else
                unitALancer = (dé1.Next(3) + 1) * 2; //1/3 Fusillers, 1/3MiniGuns et Jeeps
            return unitALancer;
        }

        private int SendSupport(int unitALancer, User enemy)
        {
            // Mais on va pas support a chaque fois... on va pas la jouer tout le temps defensive donc on refait
            // un test: si on a une grosse armée en face, autant jouer defensif... sinon nan
            Unit lastUnit = this.PeonList[PeonList.Count - 1];
            if (enemy.PeonList.Count > 4)
                switch (lastUnit.UnitType)
                {

                    case 9:
                    case 8:
                    case 2:
                        unitALancer = 1; // Sniper
                        break;
                    case 6:
                        if (PeonList.Count > 1 && this.PeonList[PeonList.Count - 2].UnitType != 5) // On voudrait pas qu'il fasse only Bazook
                            unitALancer = 5; // Un autre bazzook
                        break;
                    case 5:
                        unitALancer = 5; // Bazoook!!                                
                        break;
                    case 4:
                        unitALancer = 4; // Un MiniGun
                        break;
                }
            return unitALancer;
        }

        private Unit popUnit(int unitALancer)
        {
            Unit unit;
            switch (unitALancer)
            {
                case 0:
                    unit = new UnitLight1(this);
                    break;
                case 1:
                    unit = new UnitLight2(this);
                    break;
                case 2:
                    unit = new UnitLight3(this);
                    break;
                case 3:
                    unit = new UnitMedium1(this);
                    break;
                case 4:
                    unit = new UnitMedium2(this);
                    break;
                case 5:
                    unit = new UnitMedium3(this);
                    break;
                case 6:
                    unit = new UnitHeavy1(this);
                    break;
                case 7:
                    unit = new UnitHeavy2(this);
                    break;
                case 8:
                    unit = new UnitHeavy3(this);
                    break;
                default:
                    unit = new UnitLight3(this);

                    break;
            }
            return unit;
        }

        private void MakeResearch(Research rsrch, int hauteur)
        {
            if (!rsrch.isDeveloped)
            {
                //Console.WriteLine(rsrch.name);
                rsrch.isDeveloping = true;
            }
            rsrch.Update();
            if (rsrch.isDeveloped)
                if (rsrch.SonLeft != rsrch.SonRight)// Si on est pas au dernier level
                    if (rsrchTemplate[hauteur])
                        MakeResearch(rsrch.SonLeft, hauteur + 1);
                    else
                        MakeResearch(rsrch.SonRight, hauteur + 1);
        }

        #endregion

    }

}

/*      
               _____
       _..--'''@   @'''--.._
     .'   @_/-//-\/>/>'/ @  '.
    (  @  /_<//<'/----------^-)
    |'._  @     //|###########|
    |~  ''--..@|',|}}}}}}}}}}}|
    |  ~   ~   |/ |###########|
    | ~~  ~   ~|./|{{{{{{{{{{{|
     '._ ~ ~ ~ |,/`````````````
       ''--.~.|/
 * 
 * THE CAKE IS A LIE ...
*/